<template>
  <div>
    <el-radio-group
      v-if="showRaw"
      v-model="currentRadio"
      @change="(val) => { show = (val === 'json') }"
    >
      <el-radio label="json">json</el-radio>
      <el-radio label="raw">raw</el-radio>
    </el-radio-group>
    <div class="json-schema-vue-editor">
      <schema-json
        v-if="show"
        :data="schemaData"
        :editor-id="editorId"
        :disabled="disabled"
      />
      <el-input
        v-if="!show"
        v-model="schemaDataJson"
        :autosize="{ minRows: 2, maxRows: 20 }"
        :disabled="disabled"
        type="textarea"
        placeholder="Please input"
        style="padding-top: 8px"
      >
      </el-input>
      <!-- 高级设置弹窗 -->
      <StringDialog
        ref="stringDialog"
        :init-data="settingModalData"
      />
      <NumberDialog
        ref="numberDialog"
        :init-data="settingModalData"
      />
      <IntegerDialog
        ref="integerDialog"
        :init-data="settingModalData"
      />
      <BooleanDialog
        ref="booleanDialog"
        :init-data="settingModalData"
      />
      <ObjectDialog
        ref="objectDialog"
        :init-data="settingModalData"
      />
      <ArrayDialog
        ref="arrayDialog"
        :init-data="settingModalData"
      />
    </div>
  </div>
</template>

<script>
import "./jsonschema.scss";
import set from "lodash/set";
import get from "lodash/get";
import unset from "lodash/unset";
import cloneDeep from "lodash/cloneDeep";
import {
  defineComponent,
  getCurrentInstance,
  nextTick,
  onBeforeUnmount,
  onMounted,
  reactive,
  toRefs,
  watch,
  provide,
} from "vue";
import SchemaJson from "./Schema/index.vue";
import {
  StringDialog,
  NumberDialog,
  IntegerDialog,
  BooleanDialog,
  ObjectDialog,
  ArrayDialog,
} from "./Dialog";
import {
  SCHEMA_TYPE,
  defaultInitSchemaData,
  uuid,
  defaultSchema,
  deleteData,
  JSONPATH_JOIN_CHAR,
} from "./helper/utils";

export default defineComponent({
  name: "vue-json-schema-editor",
  components: {
    SchemaJson,
    StringDialog,
    NumberDialog,
    IntegerDialog,
    BooleanDialog,
    ObjectDialog,
    ArrayDialog,
  },
  props: {
    schema: {
      type: Object,
      default: () => {},
    },
    // 禁用
    disabled: {
      type: Boolean,
      default: false,
    },
    // 配置
    config: {
      type: Object,
      default: () => {},
    },
    // 显示单选框组,切换显示json/raw
    showRaw: {
      type: Boolean,
      default: false,
    }
  },
  setup(props, { emit }) {
    const initSchema = props.schema || defaultInitSchemaData;
    const state = reactive({
      editorId: uuid(),
      show: true,
      schemaTypes: SCHEMA_TYPE,
      schemaData: initSchema,
      schemaDataJson: JSON.stringify(initSchema, null, 2),
      // 弹窗配置
      settingModalData: {},
      // 当前选中radio
      currentRadio: 'json',
    });

    watch(
      () => state.schemaData,
      (newVal) => {
        state.schemaDataJson = JSON.stringify(newVal, null, 2);
      },
      {
        deep: true,
      }
    )

    const { proxy } = getCurrentInstance();
    onMounted(() => {
      proxy.mittBus.on(
        `schema-update-${state.editorId}`,
        handleSchemaUpdateEvent
      );
    });
    onBeforeUnmount(() => {
      proxy.mittBus.off(
        `schema-update-${state.editorId}`,
        handleSchemaUpdateEvent
      );
    });
    const handleSchemaUpdateEvent = (options) => {
      const { eventType, ...opts } = options;
      switch (eventType) {
        case "add-field":
          addFieldAction(opts);
          break;
        case "delete-field":
          deleteFieldAction(opts);
          break;
        case "update-field-name":
          updateFieldNameAction(opts);
          break;
        case "toggle-required":
          enableRequireAction(opts);
          break;
        case "schema-type":
          handleChangeType(opts);
          break;
        case "setting":
          handleSettingAction(opts);
          break;
        case "save-setting":
          handleSaveSetting(opts)
          break;
        default:
          break;
      }
    };

    /**
     * 处理新增字段
     * @param isChild 新增子节点
     * @param action 字段和路径
     */
    const addFieldAction = (opts) => {
      const { isChild, name, prefix } = opts;
      // 父级路径
      let parentPrefix = "";
      // 必需字段路径
      let requirePrefix = [];
      if (isChild) {
        const tempArr = [].concat(prefix, name);
        parentPrefix = tempArr.concat("properties").join(JSONPATH_JOIN_CHAR);
        requirePrefix = [...tempArr];
      } else {
        parentPrefix = prefix.join(JSONPATH_JOIN_CHAR);
        const tempPrefix = [].concat(prefix);
        tempPrefix.pop();
        requirePrefix = tempPrefix;
      }
      let newPropertiesData = {};
      const ranName = "field_" + uuid();
      const propertiesData = get(state.schemaData, parentPrefix);
      newPropertiesData = Object.assign({}, propertiesData);
      newPropertiesData[ranName] = cloneDeep(defaultSchema.string);
      const cloneSchema = cloneDeep(state.schemaData);
      set(cloneSchema, parentPrefix, newPropertiesData);

      // add required
      let pRequiredData = null;
      if (!requirePrefix.length) {
        // 一级属性
        pRequiredData = cloneSchema;
      } else {
        pRequiredData = get(cloneSchema, requirePrefix);
      }
      const requiredData = [].concat(pRequiredData.required || []);
      requiredData.push(ranName);
      requirePrefix.push("required");
      set(cloneSchema, requirePrefix, requiredData);
      // update schema
      state.schemaData = cloneSchema;
      forceUpdate();
      handleEmitChange(cloneSchema);
    };

    // 删除字段
    const deleteFieldAction = (opts) => {
      const { name, prefix } = opts;
      const curFieldPath = [].concat(prefix, name).join(JSONPATH_JOIN_CHAR);
      const cloneSchema = cloneDeep(state.schemaData);
      unset(cloneSchema, curFieldPath);

      state.schemaData = cloneSchema;
      forceUpdate();
      handleEmitChange(cloneSchema);
    };

    // 更新字段名称
    const updateFieldNameAction = (opts) => {
      const { value, name, prefix } = opts;
      let requirePrefix = [];
      const prefixCopy = cloneDeep(prefix);
      prefixCopy.pop();
      // 上级 required路径
      requirePrefix = prefixCopy;
      const parentPrefix = prefix.join(JSONPATH_JOIN_CHAR);
      // 父级路径,example: properties.field_2.properties.field_3.properties.field_4.items.properties
      const curFieldPath = prefix.concat(name).join(JSONPATH_JOIN_CHAR);
      const cloneSchema = cloneDeep(state.schemaData);
      // // 原来的值
      const propertiesData = get(cloneSchema, curFieldPath);
      // // 移除
      unset(cloneSchema, curFieldPath);
      // // 添加
      set(cloneSchema, `${parentPrefix}.${value}`, propertiesData);

      // update required name,获取必需字段数组
      let pRequiredData = null;
      if (!requirePrefix.length) {
        // 一级属性
        pRequiredData = cloneSchema;
      } else {
        pRequiredData = get(cloneSchema, requirePrefix);
      }
      let requiredData = [].concat(pRequiredData.required || []);
      // 遍历修改必需字段数组
      requiredData = requiredData.map((item) => {
        if (item === name) return value;
        return item;
      });
      requirePrefix.push("required");
      // 修改必需字段数组
      set(cloneSchema, requirePrefix, requiredData);

      state.schemaData = cloneSchema;
      forceUpdate();
      handleEmitChange(cloneSchema);
    };

    // schema 字段是否必须变化
    const enableRequireAction = (opts) => {
      const { prefix, name, required } = opts;
      const prefixCopy = cloneDeep(prefix);
      prefixCopy.pop();
      const parentKeys = [...prefixCopy];
      const parentPrefix = parentKeys.join(JSONPATH_JOIN_CHAR);
      const cloneSchema = cloneDeep(state.schemaData);
      let parentData = null;
      if (!parentPrefix) {
        // 一级属性
        parentData = cloneSchema;
      } else {
        parentData = get(cloneSchema, parentPrefix);
      }
      const requiredData = [].concat(parentData.required || []);
      const index = requiredData.indexOf(name);
      // 取消必填
      if (!required && index >= 0) {
        requiredData.splice(index, 1);
        parentKeys.push("required");
        if (requiredData.length === 0) {
          deleteData(cloneSchema, parentKeys);
        } else {
          set(cloneSchema, parentKeys, requiredData);
        }
      } else if (required && index === -1) {
        // 必填
        requiredData.push(name);
        parentKeys.push("required");
        set(cloneSchema, parentKeys, requiredData);
      }
      forceUpdate(cloneSchema);
      handleEmitChange(cloneSchema);
    };

    // schema 类型变化
    const handleChangeType = (opts) => {
      const { value, name, prefix } = opts;
      const parentPrefix = [].concat(prefix, name);
      const cloneSchema = cloneDeep(state.schemaData);
      const parentData = get(cloneSchema, parentPrefix);
      // 重置当前 schema 为默认值
      const newParentDataItem = defaultSchema[value];
      // 保留备注信息
      const parentDataItem = {};
      if (parentData.description) {
        parentDataItem.description = parentData.description;
      }
      const newParentData = Object.assign(
        {},
        newParentDataItem,
        parentDataItem
      );
      set(cloneSchema, parentPrefix, newParentData);

      state.schemaData = cloneSchema;
      forceUpdate();
      handleEmitChange(cloneSchema);
    };
    
    // 高级设置
    const handleSettingAction = (opts) => {
      const { schemaType, name, prefix, isRoot } = opts;
      // console.log(schemaType)
      let parentData;
      if (isRoot) {
        parentData = state.schemaData;
      } else {
        const parentPrefix = [].concat(prefix, name);
        parentData = get(state.schemaData, parentPrefix);
      }

      state.settingModalData = {
        schemaType,
        name,
        isRoot,
        prefix,
        editorId: state.editorId,
        ...parentData,
      };
      if (proxy.$refs[`${schemaType}Dialog`]) {
        proxy.$refs[`${schemaType}Dialog`].onOpen();
      }
    };

    // 高级设置更新 schema
    const handleSaveSetting = (opts) => {
      const { name, prefix, newData, isRoot } = opts
      const cloneSchema = cloneDeep(state.schemaData)
      if (isRoot) {
        Object.assign(cloneSchema, { ...newData })
      } else {
        const parentPrefix = [].concat(prefix, name)
        const oldData = get(cloneSchema, parentPrefix)
        set(cloneSchema, parentPrefix, { ...oldData, ...newData })
      }
      state.schemaData = cloneSchema
      forceUpdate()
      handleEmitChange(cloneSchema)
    }

    // 解决嵌套对象属性无法刷新页面问题
    const forceUpdate = (data) => {
      const temp = data || state.schemaData;
      state.schemaData = {};
      nextTick(() => {
        state.schemaData = temp;
      });
    };

    // schema改变事件
    const handleEmitChange = (schema) => {
      emit("schema-change", schema);
    };

    // 合并默认配置
    const defaultConfig = {
      // 显示默认值输入框
      showDefaultValue: true,
      // 显示描述输入框
      showDescrption: true,
      // 显示操作按钮
      showBtn: true,
      // 缩进
      indent: '15px',
      ...props.config,
    };
    provide('schemaConfig', defaultConfig);
    return {
      ...toRefs(state),
    };
  },
});
</script>

<style lang="scss" scoped></style>
